package com.example.common.handler;

import com.example.common.base.BaseEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 参考 http://blog.csdn.net/fighterandknight/article/details/51520595
 * 解决 Mybatis 中枚举的问题，
 * 获取 ResultSet 的值都是获取字符串的，然后比较字符串，以便通用。
 *
 * @description 自定义
 */
public class BaseEnumTypeHandler<E extends BaseEnum> extends BaseTypeHandler<E> {

    /**
     * 枚举的class
     */
    private Class<E> type;
    /**
     * 枚举的每个子类枚
     */
    private E[] enums;

    /**
     * 一定要有默认的构造函数，不然抛出 not found method 异常
     */
    public BaseEnumTypeHandler() {
    }

    /**
     * 设置配置文件设置的转换类以及枚举类内容，供其他方法更便捷高效的实现
     *
     * @param type 配置文件中设置的转换类
     */
    public BaseEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        this.enums = type.getEnumConstants();
        if (this.enums == null) {
            throw new IllegalArgumentException(type.getSimpleName()
                    + " does not represent an enum type.");
        }
    }

    /**
     * Java类型 转换为对应的数据库类型
     *
     * @param parameter 枚举对象
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter,
                                    JdbcType jdbcType) throws SQLException {
        /*
         * BaseTypeHandler已经帮我们做了parameter的null判断
         * 数据库存储的是枚举的值，所以我们这里使用 value ， 如果需要存储 name，可以自定义修改
         */
        if (jdbcType == null) {
            ps.setObject(i, parameter.getValue());
        } else {
            ps.setObject(i, parameter.getValue(), jdbcType.TYPE_CODE);
        }
    }

    /**
     * 用于定义通过字段名称获取字段数据时，如何把数据库类型转换为对应的Java类型
     *
     * @param columnName 字段名
     */
    @Override
    public E getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        Object code = rs.getObject(columnName);
        if (rs.wasNull()) {
            return null;
        } else {
            return locateEnumStatus(code);
        }
    }

    /**
     * 用于定义通过字段索引获取字段数据时，如何把数据库类型转换为对应的Java类型
     *
     * @param columnIndex 字段索引
     */
    @Override
    public E getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        Object code = rs.getObject(columnIndex);
        if (rs.wasNull()) {
            return null;
        } else {
            return locateEnumStatus(code);
        }
    }

    /**
     * 用定义调用存储过程后，如何把数据库类型转换为对应的Java类型。
     *
     * @param columnIndex 字段索引
     */
    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        String code = cs.getString(columnIndex);
        if (cs.wasNull()) {
            return null;
        } else {
            return locateEnumStatus(code);
        }
    }

    /**
     * 枚举类型转换，由于上面的构造函数获取了枚举的子类 enums，让遍历更加高效快捷，
     * <p>
     * 将取出来的值 判断类型然后转换成字符串 进行比较，
     *
     * @param value 数据库中存储的自定义value属性
     * @return value 对应的枚举类
     */
    private E locateEnumStatus(Object value) {
        if (value == null) {
            throw new NullPointerException("数据库结果值is null " + value);
        }

        if (value instanceof Integer) {
            for (E e : enums) {
                if (e.getValue() == value) {
                    return e;
                }
            }
            throw new IllegalArgumentException("未知的枚举类型：" + value + ",请核对"
                    + type.getSimpleName());
        }

        if (value instanceof String) {
            for (E e : enums) {
                if (value.equals(e.getValue())) {
                    return e;
                }
            }
            throw new IllegalArgumentException("未知的枚举类型：" + value + ",请核对"
                    + type.getSimpleName());
        }

        throw new IllegalArgumentException("未知的枚举类型：" + value + ",请核对"
                + type.getSimpleName());
    }
}
